﻿using System;
using System.Xml;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
using System.Reflection;
using System.Collections.Specialized;
using Microsoft.SharePoint.Security;
using System.Runtime.InteropServices;
using System.Security.Permissions;

namespace Version3.SharePoint
{
    //this class is our field defintion it inherits SPFieldMultiLineText
    //mostly so that we will follow the pattern for NTEXT columns which
    //can store lots of text. 
    [CLSCompliant(false)]
    [Guid("E9EFCFF6-944C-4870-A738-69363516A1CE")]
    public class ReadUnreadMarksDataField : SPFieldMultiLineText
    {
        #region Declarations

        private static StringDictionary _NewColumnProperties = new StringDictionary();  // there are some odd circumstances where the property editor
                                                                                        // is recreated withtout saving itself (probably due to postback
                                                                                        // so this is a global that we use to keep information about newly
                                                                                        // created columns
                                                                                        // the pattern for the soluciton to the apparent bug in SharePoint
                                                                                        // came from a thread :
                                                                                        //http://jack.whyyoung.com/blog/www-sharepointblogs-com-MainFeed-aspx-GroupID-3/The-solution-to-saving-properties-i.htm
        internal const string NO_CONTEXT = "NO*SP*CONTEXT";                            
        internal const string COLUMN_RENDER_MODE_KEY = "ColumnRenderMode";
        internal const string CLEAR_READ_MARKS_ON_UPDATE_KEY = "ClearReadMarksOnUpdate";
        internal const string VISIBLE_ON_FORM_KEY = "VisibleOnForm";
        internal const string SHOW_INLINE_EDITING_TOOLS_KEY = "ShowInlineEditingTools";
        internal const string WRITE_DIRECT_KEY = "WriteDirect";
        internal const string READ_IMAGE_URL_KEY = "ReadImageURL";
        internal const string UNREAD_IMAGE_URL_KEY = "UnreadImageURL";
        internal const string UNREAD_HTML_COLOR_KEY = "UnreadHTMLColor";
        internal const string ICON_DISPLAY = "Iconic";
        internal const string BOLD_DISPLAY = "BoldDisplay";
        internal const string UNREAD_COLOR = "";

        public const string READ_MARK_DELIMITER = ".";

        private string _ContextID = NO_CONTEXT;
        private string _ColumnRenderMode = string.Empty;
        private string _ReadImageUrl = string.Empty;
        private string _UnreadImageUrl = string.Empty;
        private string _UnreadHTMLColor = string.Empty;
        private bool _ClearReadMarksOnUpdate = false;
        private bool _ShowInlineEditingTools = false;
        private bool _WriteDirect = false;
        private bool _VisibleOnForm = false;
        #endregion

        #region Constructors
        public ReadUnreadMarksDataField(SPFieldCollection fields, string fieldName)
            : base(fields, fieldName)
        {
            try
            {
                _ContextID = SPContext.Current.GetHashCode().ToString();
            }
            catch
            {
                _ContextID = NO_CONTEXT;
            }
            ReadCustomProperties();
        }

        public ReadUnreadMarksDataField(SPFieldCollection fields, string typeName, string displayName)
            : base(fields, typeName, displayName)
        {
            try
            {
                _ContextID = SPContext.Current.GetHashCode().ToString();
            }
            catch
            {
                _ContextID = NO_CONTEXT;
            }
            ReadCustomProperties();
        }

        #endregion

        #region SPField imlementation
        public override Type FieldValueType
        {
            get
            {
                return typeof(Version3.SharePoint.ReadUnreadMarksDataField);
            }
        }

        public override BaseFieldControl FieldRenderingControl
        {
            [SharePointPermission(SecurityAction.LinkDemand, ObjectModel = true)]
            get
            {
                BaseFieldControl fieldControl = new ReadUnreadMarksDataFieldControl(this);
                fieldControl.FieldName = this.InternalName;
                return fieldControl;
            }
        }

        public override string GetValidatedString(object value)
        {
            string returnValue = string.Empty;
            if (value is ReadUnreadMarksDataFieldValue)
            {
                ReadUnreadMarksDataFieldValue castValue = (ReadUnreadMarksDataFieldValue)value;
                returnValue = castValue.ToString();
            }
            else
            {
                if (value is string)
                {
                    returnValue = (string)value;
                }
            }
            return returnValue;
        }

        public override object GetFieldValue(string value)
        {
            ReadUnreadMarksDataFieldValue returnValue = new ReadUnreadMarksDataFieldValue(value);
            return returnValue;
        }

        public override string GetFieldValueAsText(object value)
        {
            ReadUnreadMarksDataFieldValue returnValue = new ReadUnreadMarksDataFieldValue(value);
            return returnValue.ToString();
        }

        public override string GetFieldValueAsHtml(object value)
        {
            ReadUnreadMarksDataFieldValue returnValue = new ReadUnreadMarksDataFieldValue(value);
            return returnValue.ToHTMLString();
        }

        public override bool Filterable
        {
            get
            {
                return false;
            }
        }

        public override bool Sortable
        {
            get
            {
                return false;
            }
        }

        public override bool FilterableNoRecurrence
        {
            get
            {
                return false;
            }
        }

        public override object PreviewValueTyped
        {
            get
            {
                return null;
            }
        }

        public override void OnAdded(SPAddFieldOptions op)
        {
            Update();
            base.OnAdded(op);
        }

        public override void OnDeleting()
        {
            VerifyEventHandler();
            base.OnDeleting();
        }

        public override void Update()
        {
            WriteCustomProperties();
            base.Update();
        }

        public override string DefaultValue
        {
            get
            {
                string returnValue = base.DefaultValue;
                if (System.Web.HttpContext.Current.User.Identity.IsAuthenticated)
                {
                    SPUser currentUser = UtilityMethods.EnsureUser(System.Web.HttpContext.Current.User.Identity.Name);
                    returnValue = READ_MARK_DELIMITER + currentUser.ID.ToString() + READ_MARK_DELIMITER;
                }
                return returnValue;
            }
            set
            {
                base.DefaultValue = value;
            }
        }

        public override object DefaultValueTyped
        {
            get
            {
                return new ReadUnreadMarksDataFieldValue(this.DefaultValue);

            }
        }

        #endregion

        #region Property management
        private void ReadCustomProperties()
        {
            _WriteDirect = bool.Parse(WrappedGetCustomProperty(WRITE_DIRECT_KEY, false.ToString()));
            _ShowInlineEditingTools = bool.Parse(WrappedGetCustomProperty(SHOW_INLINE_EDITING_TOOLS_KEY, false.ToString()));
            _VisibleOnForm = bool.Parse(WrappedGetCustomProperty(VISIBLE_ON_FORM_KEY, false.ToString()));
            _ClearReadMarksOnUpdate = bool.Parse(WrappedGetCustomProperty(CLEAR_READ_MARKS_ON_UPDATE_KEY, false.ToString()));
            _ColumnRenderMode = WrappedGetCustomProperty(COLUMN_RENDER_MODE_KEY, string.Empty);
            _UnreadImageUrl = WrappedGetCustomProperty(UNREAD_IMAGE_URL_KEY, string.Empty);
            _ReadImageUrl = WrappedGetCustomProperty(READ_IMAGE_URL_KEY, string.Empty);
            _UnreadHTMLColor  = WrappedGetCustomProperty(UNREAD_HTML_COLOR_KEY, string.Empty);
        }

        private void WriteCustomProperties()
        {
            this.SetCustomProperty(COLUMN_RENDER_MODE_KEY, this.ColumnRenderMode);
            this.SetCustomProperty(CLEAR_READ_MARKS_ON_UPDATE_KEY, this.ClearReadMarksOnUpdate.ToString());
            this.SetCustomProperty(VISIBLE_ON_FORM_KEY, this.VisibleOnForm.ToString());
            this.SetCustomProperty(SHOW_INLINE_EDITING_TOOLS_KEY, this.ShowInlineEditingTools.ToString());
            this.SetCustomProperty(WRITE_DIRECT_KEY, this.WriteDirect.ToString());
            this.SetCustomProperty(READ_IMAGE_URL_KEY, this.ReadImageUrl);
            this.SetCustomProperty(UNREAD_IMAGE_URL_KEY, this.UnreadImageUrl);
            this.SetCustomProperty(UNREAD_HTML_COLOR_KEY, this.UnreadHTMLColor);

            //Apparently CAML cant access custom properties in a view 
            //DisplayPattern using normal syntax (or at least I cant figure it out)
            //so we will put the value in an attribute of the field where all the built
            //in values are, this seems to work. I dont know if its a bug or if I 
            //just dont understand how to query custom values in CAML display patterns.

            XmlDocument xmlSchema = new XmlDocument();
            xmlSchema.LoadXml(this.SchemaXml);
            XmlNode fieldNode = xmlSchema.SelectSingleNode("/Field");
            if (null != fieldNode)
            {
                //Add our attribute to the field XML
                XmlAttribute crmAttribute = fieldNode.Attributes[COLUMN_RENDER_MODE_KEY];
                if (null == crmAttribute)
                {
                    crmAttribute = xmlSchema.CreateAttribute(COLUMN_RENDER_MODE_KEY);
                    fieldNode.Attributes.SetNamedItem(crmAttribute);
                }
                crmAttribute.InnerText = this.ColumnRenderMode;
                

                XmlAttribute crmuAttribute = fieldNode.Attributes[CLEAR_READ_MARKS_ON_UPDATE_KEY];
                if (null == crmuAttribute)
                {
                    crmuAttribute = xmlSchema.CreateAttribute(CLEAR_READ_MARKS_ON_UPDATE_KEY);
                    fieldNode.Attributes.SetNamedItem(crmuAttribute);
                }
                crmuAttribute.InnerText = this.ClearReadMarksOnUpdate.ToString().ToUpper();

                XmlAttribute rdAttribute = fieldNode.Attributes[SHOW_INLINE_EDITING_TOOLS_KEY];
                if (null == rdAttribute)
                {
                    rdAttribute = xmlSchema.CreateAttribute(SHOW_INLINE_EDITING_TOOLS_KEY);
                    fieldNode.Attributes.SetNamedItem(rdAttribute);
                }
                rdAttribute.InnerText = this.ShowInlineEditingTools.ToString().ToUpper();
 
                XmlAttribute vofAttribute = fieldNode.Attributes[VISIBLE_ON_FORM_KEY];
                if (null == vofAttribute)
                {
                    vofAttribute = xmlSchema.CreateAttribute(VISIBLE_ON_FORM_KEY);
                    fieldNode.Attributes.SetNamedItem(vofAttribute);
                }
                vofAttribute.InnerText = this.VisibleOnForm.ToString().ToUpper();
 
                XmlAttribute wdAttribute = fieldNode.Attributes[WRITE_DIRECT_KEY];
                if (null == wdAttribute)
                {
                    wdAttribute = xmlSchema.CreateAttribute(WRITE_DIRECT_KEY);
                    fieldNode.Attributes.SetNamedItem(wdAttribute);
                }
                wdAttribute.InnerText = this.WriteDirect.ToString().ToUpper();

                XmlAttribute rimgAttribute = fieldNode.Attributes[READ_IMAGE_URL_KEY];
                if (null == rimgAttribute)
                {
                    rimgAttribute = xmlSchema.CreateAttribute(READ_IMAGE_URL_KEY);
                    fieldNode.Attributes.SetNamedItem(rimgAttribute);
                }
                rimgAttribute.InnerText = this.ReadImageUrl;

                XmlAttribute urimgAttribute = fieldNode.Attributes[UNREAD_IMAGE_URL_KEY];
                if (null == urimgAttribute)
                {
                    urimgAttribute = xmlSchema.CreateAttribute(UNREAD_IMAGE_URL_KEY);
                    fieldNode.Attributes.SetNamedItem(urimgAttribute);
                }
                urimgAttribute.InnerText = this.UnreadImageUrl;

                XmlAttribute urcolorAttribute = fieldNode.Attributes[UNREAD_HTML_COLOR_KEY];
                if (null == urcolorAttribute)
                {
                    urcolorAttribute = xmlSchema.CreateAttribute(UNREAD_HTML_COLOR_KEY);
                    fieldNode.Attributes.SetNamedItem(urcolorAttribute);
                }
                urcolorAttribute.InnerText = this.UnreadHTMLColor;
            }

            //Ok now reset it, after we do this dont monkey with the 
            //properties until its been updated
            this.SchemaXml = fieldNode.OuterXml;

            //Clear the new column property flags
            lock (_NewColumnProperties)
            {
                string contextKey = _ContextID + CLEAR_READ_MARKS_ON_UPDATE_KEY;
                if (_NewColumnProperties.ContainsKey(contextKey)) _NewColumnProperties.Remove(contextKey);

                contextKey = _ContextID + COLUMN_RENDER_MODE_KEY;
                if (_NewColumnProperties.ContainsKey(contextKey)) _NewColumnProperties.Remove(contextKey);

                contextKey = _ContextID + SHOW_INLINE_EDITING_TOOLS_KEY;
                if (_NewColumnProperties.ContainsKey(contextKey)) _NewColumnProperties.Remove(contextKey);

                contextKey = _ContextID + VISIBLE_ON_FORM_KEY;
                if (_NewColumnProperties.ContainsKey(contextKey)) _NewColumnProperties.Remove(contextKey);

                contextKey = _ContextID + WRITE_DIRECT_KEY;
                if (_NewColumnProperties.ContainsKey(contextKey)) _NewColumnProperties.Remove(contextKey);

                contextKey = _ContextID + UNREAD_IMAGE_URL_KEY;
                if (_NewColumnProperties.ContainsKey(contextKey)) _NewColumnProperties.Remove(contextKey);

                contextKey = _ContextID + READ_IMAGE_URL_KEY;
                if (_NewColumnProperties.ContainsKey(contextKey)) _NewColumnProperties.Remove(contextKey);

                contextKey = _ContextID + UNREAD_HTML_COLOR_KEY;
                if (_NewColumnProperties.ContainsKey(contextKey)) _NewColumnProperties.Remove(contextKey);

            }
           
            VerifyEventHandler();

        }

        private void VerifyEventHandler()
        {
            if (null != this.ParentList)
            {
                //First remove all of our event handlers
                UninstallAllEventHandlers();
                bool clearReadMarks = false;
                //Now check to see if any fields need it for clearing read marks
                foreach (SPField readUnreadField in this.ParentList.Fields)
                {
                    if (typeof(ReadUnreadMarksDataField).Name == readUnreadField.TypeAsString)
                    {
                        //we are not fully saved yet so we cant use a cast
                        //to figure this out, but luckily we updated the SchemaXml
                        XmlDocument xmlSchema = new XmlDocument();
                        xmlSchema.LoadXml(readUnreadField.SchemaXml);
                        XmlNode fieldNode = xmlSchema.SelectSingleNode("/Field");
                        if (null != fieldNode)
                        {
                            if (!clearReadMarks)
                            {
                                XmlAttribute crmuAttribute = fieldNode.Attributes[CLEAR_READ_MARKS_ON_UPDATE_KEY];
                                if (null != crmuAttribute)
                                {
                                    clearReadMarks = bool.Parse(crmuAttribute.InnerText);
                                }
                            }
                        }
                        if (clearReadMarks) break;
                    }

                }
                if (clearReadMarks)
                {
                    InstallOneEventHandler();
                }
            }
        }

        private void InstallOneEventHandler()
        {
            if (null != this.ParentList)
            {
                //Install our ReadUnreadListItemEventHandler class for ItemUpdated 
                string eventAssembly = Assembly.GetExecutingAssembly().FullName;
                string eventClass = typeof(ReadUnreadListItemEventHandler).ToString();
                this.ParentList.EventReceivers.Add(SPEventReceiverType.ItemUpdated, eventAssembly, eventClass);
                this.ParentList.EventReceivers.Add(SPEventReceiverType.ItemAdded, eventAssembly, eventClass);
                this.ParentList.Update();
            }
        }

        private void UninstallAllEventHandlers()
        {
            if (null != this.ParentList)
            {
                //Remove all regisered copies of our ReadUnreadListItemEventHandler 
                string eventAssembly = Assembly.GetExecutingAssembly().FullName;
                string eventClass = typeof(ReadUnreadListItemEventHandler).ToString();
                bool updateList = false;
                for (int eventIndex = this.ParentList.EventReceivers.Count; eventIndex > 0; eventIndex--)
                {
                    SPEventReceiverDefinition eventDefinition = this.ParentList.EventReceivers[eventIndex - 1];
                    if (eventDefinition.Assembly == eventAssembly)
                    {
                        if (eventDefinition.Class == eventClass)
                        {
                            eventDefinition.Delete();
                            updateList = true;
                        }
                    }

                }
                if (updateList)
                {
                    this.ParentList.Update();
                }
            }
        }

        private string WrappedGetCustomProperty(string propertyName, string defaultValue)
        {
            //Since we have the static collection of pending updates
            //we need to check it first , if its not there we will
            //try to read it as a custom property
            string returnValue = string.Empty;
            lock (_NewColumnProperties)
            {
                string contextKey = _ContextID + propertyName;
                if (_NewColumnProperties.ContainsKey(contextKey))
                {
                    returnValue = _NewColumnProperties[contextKey];
                }
            }
            if (string.IsNullOrEmpty(returnValue))
            {
                try
                {
                    object sharePointCastValue = this.GetCustomProperty(propertyName);
                    returnValue = this.GetCustomProperty(propertyName) as string;
                    if (string.IsNullOrEmpty(returnValue))
                    {
                        if (null != sharePointCastValue)
                        {
                            returnValue = sharePointCastValue.ToString();
                        }
                    }
                }
                catch
                {
                    returnValue = defaultValue;
                }
            }
            if (string.IsNullOrEmpty(returnValue)) returnValue = defaultValue;
            return returnValue;
        }

        public void SetNewColumnPropertyCacheValue(string propertyKey, string propertyValue)
        {
            //Add a new string value to the static new property cache
            lock (_NewColumnProperties)
            {
                string contextKey = _ContextID + propertyKey;
                _NewColumnProperties[contextKey] = propertyValue;
            }
        }
        #endregion

        #region Public properties

        public bool VisibleOnForm
        {
            //Class property, if not set then the
            //form javascript for "hiding" the label 
            //will be emmited on New and Display forms
            get
            {
                lock (_NewColumnProperties)
                {
                    string contextKey = _ContextID + VISIBLE_ON_FORM_KEY;
                    if (_NewColumnProperties.ContainsKey(contextKey))
                    {
                        return bool.Parse(_NewColumnProperties[contextKey]);
                    }
                    else
                    {
                        return _VisibleOnForm;
                    }
                }
            }
            set
            {
                _VisibleOnForm = value;
            }
        }

        public bool ClearReadMarksOnUpdate
        {
            //Class property, if set then install the 
            //event handler for clearing read marks
            //when an item is updated
            get
            {
                lock (_NewColumnProperties)
                {
                    string contextKey = _ContextID + CLEAR_READ_MARKS_ON_UPDATE_KEY;
                    if (_NewColumnProperties.ContainsKey(contextKey))
                    {
                        return bool.Parse(_NewColumnProperties[contextKey]);
                    }
                    else
                    {
                        return _ClearReadMarksOnUpdate;
                    }
                }
            }
            set
            {
                _ClearReadMarksOnUpdate = value;
            }
        }

        public bool ShowInlineEditingTools
        {
            //Class property, if  set then the column
            //also emits the raw data , can get quite large
            //but sometimes necessary for debugging
            get
            {
                lock (_NewColumnProperties)
                {
                    string contextKey = _ContextID + SHOW_INLINE_EDITING_TOOLS_KEY;
                    if (_NewColumnProperties.ContainsKey(contextKey))
                    {
                        return bool.Parse(_NewColumnProperties[contextKey]);
                    }
                    else
                    {
                        return _ShowInlineEditingTools;
                    }
                }
            }
            set
            {
                _ShowInlineEditingTools = value;
            }
        }

        public bool WriteDirect
        {
            //Class property, if  set then the column
            //updates are handled by a direct write
            //to the configured NTEXT column. This bypasses
            //the entire API and therfore prevents the
            //dirty detection SharePoint uses for warning
            //users about data clash conflicts. In our 
            //case its fine because nothing else writes
            //to the data field.
            get
            {
                lock (_NewColumnProperties)
                {
                    string contextKey = _ContextID + WRITE_DIRECT_KEY;
                    if (_NewColumnProperties.ContainsKey(contextKey))
                    {
                        return bool.Parse(_NewColumnProperties[contextKey]);
                    }
                    else
                    {
                        return _WriteDirect;
                    }
                }
            }
            set
            {
                _WriteDirect = value;
            }
        }

        public string UnreadHTMLColor
        {
            get
            {
                lock (_NewColumnProperties)
                {
                    string contextKey = _ContextID + UNREAD_HTML_COLOR_KEY;
                    if (_NewColumnProperties.ContainsKey(contextKey))
                    {
                        return _NewColumnProperties[contextKey];
                    }
                    else
                    {
                        return _UnreadHTMLColor;
                    }
                }
            }
            set
            {
                _UnreadHTMLColor = value;
            }
        }

        public string UnreadImageUrl
        {
            //Class property, the image URL for an unread icon
            get
            {
                lock (_NewColumnProperties)
                {
                    string contextKey = _ContextID + UNREAD_IMAGE_URL_KEY;
                    if (_NewColumnProperties.ContainsKey(contextKey))
                    {
                        return _NewColumnProperties[contextKey];
                    }
                    else
                    {
                        return _UnreadImageUrl;
                    }
                }
            }
            set
            {
                if (string.IsNullOrEmpty(value))
                {
                    value = "/_layouts/images/clscUnreadMark.gif";
                }
                //If you just type an image name then prepend the layouts 
                if (!value.StartsWith("/") && !value.StartsWith("http"))
                {
                    string[] valueParts = value.Split('/');
                    if (valueParts.Length < 2)
                    {
                        value = "/_layouts/images/" + value;
                    }
                }
                _UnreadImageUrl = value;
            }
        }
        
        public string ReadImageUrl
        {
            //Class property, the image URL for a read icon
            get
            {
                lock (_NewColumnProperties)
                {
                    string contextKey = _ContextID + READ_IMAGE_URL_KEY;
                    if (_NewColumnProperties.ContainsKey(contextKey))
                    {
                        return _NewColumnProperties[contextKey];
                    }
                    else
                    {
                        return _ReadImageUrl;
                    }
                }
            }
            set
            {
                if (string.IsNullOrEmpty(value))
                {
                    value = "/_layouts/images/clscReadMark.gif";
                }
                //If you just type an image name then prepend the layouts 
                if (!value.StartsWith("/") && !value.StartsWith("http"))
                {
                    string[] valueParts = value.Split('/');
                    if (valueParts.Length < 2)
                    {
                        value = "/_layouts/images/" + value;
                    }
                }
                _ReadImageUrl = value;
            }
        }

        public string ColumnRenderMode
        {
            //Class property, set to Iconic or BoldDisplay
            // which are simple images or javascript to highlight the row.
            get
            {
                lock (_NewColumnProperties)
                {
                    string contextKey = _ContextID + COLUMN_RENDER_MODE_KEY;
                    if (_NewColumnProperties.ContainsKey(contextKey))
                    {
                        return _NewColumnProperties[contextKey];
                    }
                    else
                    {
                        return _ColumnRenderMode;
                    }
                }
            }
            set
            {
                _ColumnRenderMode = value;
            }
        }
        #endregion

    }
}

